defmodule FizzBuzz do
  @spec is_div(integer, integer) :: boolean
  def is_div(n, d) do
    rem(n, d) == 0
  end

  @spec to_str(integer) :: binary
  def to_str(n) do
    Integer.to_string(n)
  end

  @spec is_contain(integer, integer) :: boolean
  def is_contain(n, d) do
    String.contains?(to_str(n), to_str(d))
  end

  @spec is_related(integer, integer) :: boolean
  def is_related(n, d) do
    is_div(n, d) || is_contain(n, d)
  end

  @spec number_maps :: %{3 => <<_::32>>, 5 => <<_::32>>, 7 => <<_::32>>}
  def number_maps, do: %{3 => "Fizz", 5 => "Buzz", 7 => "Muzz"}

  @spec sub_call(integer, integer) :: String
  def sub_call(n, d) do
    if(is_related(n, d), do: number_maps()[d], else: "")
  end

  @spec concat_str(integer) :: (integer, String -> String)
  def concat_str(n) do
    fn({k, _}, s) ->
      s <> sub_call(n, k) <> ""
    end
  end

  @spec call_out(integer) :: String
  def call_out(n) do
    # use reduce to loop number_maps
    r = Enum.reduce(number_maps(), "", concat_str(n))
    if(r == "", do: to_str(n), else: r)
  end
end
